package Client

import (
	"sync"
	"net"
	"time"
	"log"
	"fmt"
	"errors"
	"GoRProxy/Utils"
	"strings"
)

// 锁
var channalLockClient = sync.Mutex{}

var serviceMap = make(map[string]string)
// 服务器状态
var isServerAlive = true

func AClient(server string, targetAddresses []string) {

	var services = ""
	for _, targetAddr := range targetAddresses {
		i := strings.Index(targetAddr, ":")
		service := targetAddr[0:i]
		targetAddr = targetAddr[i+1:]
		serviceMap[service] = targetAddr
		services += service
		services += ","
	}
	services = strings.Trim(services, ",")
	log.Println("services", services)
	log.Println("Start  client", serviceMap)
	log.Println("Connect to control address  ", server)
	wg := sync.WaitGroup{}
	wg.Add(1)
	for {
		connRemote, err := net.DialTimeout("tcp", server, 10*time.Second)
		if err != nil {
			log.Println("Can't connect to remote: ", err)
			time.Sleep(10 * time.Second)
			continue
		}

		connRemote.SetReadDeadline(time.Now().Add(5 * time.Second))
		err = clientRegisterRequest(connRemote, services)
		if err != nil {
			log.Println("Can not register to server.")
			continue
		}
		connRemote.SetReadDeadline(time.Time{})

		go heartBeat(connRemote)
		for {
			sessionId, service, errr := serverStartSessionResponse(connRemote)
			if errr == nil {
				targetAddr := serviceMap[service]
				provideService(server, targetAddr, sessionId)
			} else {
				log.Println("Failed to start session with server. sleep", errr)
				connRemote.Close()
				time.Sleep(10 * time.Second)
				break
			}
		}
	}
	wg.Wait()
}

// 心跳服务.
func heartBeat(conn net.Conn) {
	for {
		time.Sleep(30 * time.Second)
		log.Println(" start HeartBeat ... ")
		channalLockClient.Lock()
		isServerAlive = false
		_, err := conn.Write(append([]byte{Utils.CMD_HEART_BEAT_REQUEST}, []byte("huang")...))
		if err != nil {
			log.Println("Failed to send heartbeat request to server, close the connection.", err)
			conn.Close()
			channalLockClient.Unlock()
			return
		}
		time.Sleep(2 * time.Second)
		if isServerAlive {
			log.Println("Get heartbeat response from server")
		} else {
			log.Println("Failed to get heartbeat response, close the connection.", err)
			conn.Close()
			channalLockClient.Unlock()
			return
		}
		log.Println("Release mgr lock")
		channalLockClient.Unlock()
		log.Println("Done heartbeat")
	}
}

func provideService(remoteAddr string, targetAddr string, sessionID int32) {
	log.Println("Connecting target host : ", targetAddr, " remote host : ", remoteAddr)
	targetConn := connectPort(targetAddr)
	if targetConn == nil {
		return
	}
	remoteConn := connectPort(remoteAddr)
	if remoteConn == nil {
		log.Println("Failed to connect to remote addr")
		return
	}
	clientReplySessionRequest(remoteConn, sessionID)
	log.Println("Begin transfer data ...")
	Utils.ConnectionExchange(targetConn, remoteConn)
}

func connectPort(remoteAddr string) net.Conn {
	conn, err := net.DialTimeout("tcp", remoteAddr, 5*time.Second)
	if err != nil {
		log.Println("Can't connect to addr: ", err)
		return nil
	}
	log.Println("Connectted:", conn.RemoteAddr())
	return conn
}

func clientRegisterRequest(conn net.Conn, service string) error {
	log.Println("clientRegisterRequest ... ")
	request := make([]byte, 1)
	request[0] = Utils.CMD_REGISTER_CLIENT_REQUEST
	serviceBytes := []byte(service)
	conn.Write(request)
	fmt.Println("Write to server:", service, serviceBytes)
	size, err := conn.Write(serviceBytes)
	fmt.Println("write result: size", size, " err :", err)
	if err != nil {
		return err
	}
	response := make([]byte, 1)
	_, err = conn.Read(response)
	if err != nil {
		return err
	}
	if response[0] != Utils.CMD_REGISTER_CLIENT_RESPONSE {
		return errors.New("Invalid response")
	} else {
		fmt.Println("Get client register response")
	}
	return nil
}

func clientReplySessionRequest(conn net.Conn, sessionId int32) error {
	fmt.Println("clientReplySessionRequest, session:", sessionId)
	data := append([]byte{Utils.CMD_CLIENT_REPLY_SESSION_REQUEST}, Utils.Int32ToBytes(sessionId)...)

	conn.Write(data)
	response := make([]byte, 1)
	conn.Read(response)
	if (response[0] != Utils.CMD_CLIENT_REPLY_SESSION_RESPONSE) {
		return errors.New("Invalid response ... ")
	} else {
		fmt.Println("Get client reply session response ")
	}
	return nil
}

func serverStartSessionResponse(conn net.Conn) (int32, string, error) {
	log.Println("ServerStartSessionResponse")
	for {
		request := make([]byte, 20)
		log.Println("Begin read cmd...")
		_, err := conn.Read(request)
		if err != nil {
			return 0, "", err
		}
		log.Println("Read cmd:", request)
		if request[0] == Utils.CMD_HEART_BEAT_RESPONSE {
			log.Println("Get heart beat cmd.")
			isServerAlive = true
			continue
		} else if request[0] != Utils.CMD_SERVER_START_SESSION_REQUEST {
			log.Println("Invalid cmd")
			return 0, "", errors.New("invalid cmd from server")
		}
		requestId := Utils.BytesToInt32(request[1:5])
		log.Println("request id:", requestId)

		serviceBuffer := request[5:]

		service := string(serviceBuffer)
		service = strings.Trim(service, "\x00")
		log.Println("Client got service:", service)

		response := make([]byte, 1)
		response[0] = Utils.CMD_SERVER_START_SESSION_RESPONSE
		log.Println("Sending response ", response)
		channalLockClient.Lock()
		conn.Write(response)
		channalLockClient.Unlock()
		log.Println("Sent CMD_SERVER_START_SESSION_RESPONSE response")
		return requestId, service, nil
	}
}
